#pragma once

#include "Socket.hpp"

#include <iostream>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>

static const uint16_t defaultport = 8082;
static const int fd_max_num = (sizeof(fd_set) * 8);
static const int defaultfd = -1;

class SelectServer
{
public:
    SelectServer(const uint16_t port = defaultport)
        : _port(port)
    {
        for (int i = 0; i < fd_max_num; i++)
        {
            //在定义时直接给初始值的话：_fd_array{-1},只会初始化第一个为-1，其他全是0。
            _fd_array[i] = defaultfd;
        }
    }
    ~SelectServer()
    {
        _listen_sockfd.Close();
    }

public:
    bool InitServer()
    {
        _listen_sockfd.Socket();
        _listen_sockfd.Bind(_port);
        _listen_sockfd.Listen();
    }
    void StartServer()
    {
        int listensock = _listen_sockfd.Fd();
        _fd_array[0] = listensock;
        while (true)
        {
            fd_set rfds;
            FD_ZERO(&rfds); // FD_ZERO是个宏函数，作用是将rfds做清空处理

            // 找到最大的文件描述符, 同时设置位图的值
            // 每次都要重新设置位图的值，因为这是一个循环，可能上一次循环时，对于OS给你返回的rfds位图标志的文件描述符
            // 你可能已经关闭了，那么就没必要让OS在监测他了，所以需要重新设置需要被监测的文件描述符。
            // 或者把就绪文件中的资源读取完了, 还需要重新进行监测，那么与其说修改rfds，不如重置一下。
            int maxfd = _fd_array[0];
            for (int i = 0; i < fd_max_num; i++)
            {
                if (_fd_array[i] == defaultfd)
                    continue;
                FD_SET(_fd_array[i], &rfds);
                maxfd = std::max(maxfd, _fd_array[i]);
            }
            // 不能直接Accept了，Accept的本质就是在检测并获取listensock上面的事件
            // 该操作相当于读操作。新连接到来了，就是读事件就绪了
            struct timeval timeout = {5, 0}; // 因为他是输入输出型参数，如果想每隔2秒超时一次，那么就需要重复设置

            int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
            switch (n)
            {
            case 0:
                std::cout << "time out " << std::endl;
                break;
            case -1:
                std::cerr << "select error" << std::endl;
                break;
            default:
                // 如果事件就绪上层不处理，则会一直提醒你，也就是select会一直返回，不会阻塞在select规定的timeout时间了
                HandlerEvent(rfds); // 处理读取rfds

                break;
            }
        }
    }
    // 处理读取位图rfd，建立连接&&进行网络操作
    void HandlerEvent(fd_set &rfds)
    {
        //_fd_array所有的文件描述符
        // fds所有 就绪 的文件描述符

        // 处理所有就绪的文件描述符
        for (int i = 0; i < fd_max_num; i++)
        {
            int fd = _fd_array[i];
            if (fd == defaultfd)
                continue;

            if (FD_ISSET(fd, &rfds))
            {
                //可以封装一个事件监听器函数
                // _listen_sockfd就绪:获取新链接
                if (fd == _listen_sockfd.Fd())
                {
                    std::string clientip;
                    uint16_t clientport;
                    // 这次的accept就一定不会阻塞在这里了，因为等的工作在select完成了。
                    int sockfd = _listen_sockfd.Accept(&clientip, &clientport);
                    if (sockfd < 0)
                        continue;
                    LOG(Info, "accept success");

                    int pos = 1;
                    for (; pos < fd_max_num; pos++)
                    {
                        if (_fd_array[pos] != defaultfd)
                            continue;
                        else
                            break;
                    }
                    if (pos == fd_max_num)
                    {
                        LOG(Waring, "server is full");
                        close(sockfd);
                    }
                    else
                    {
                        _fd_array[pos] = sockfd;
                    }
                }
                // 其他文件描述符就绪: 可以读取了
                else
                {
                    //封装一个事件派发器
                    char buffer[1024];
                    ssize_t n = read(fd, buffer, sizeof(buffer) - 1);  //bug 不一定是完整的报文，要做协议定制工作处理
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        std::cout << "get a message:" << buffer << std::endl;
                    }
                    else if (n == 0)
                    {
                        LOG(Waring, "client quit, me too close sockfd");
                        close(fd);
                        _fd_array[i] = defaultfd; // 移除文件描述符，那么到时候在告诉OS要监测的文件描述符中就不会有了
                    }
                    else
                    {
                        LOG(Waring, "read error");
                        close(fd);
                        _fd_array[i] = defaultfd;
                    }
                }
            }
        }
    }

private:
    Sock _listen_sockfd;
    uint16_t _port;
    // 辅助文件描述符值在不同函数之间互相传递
    int _fd_array[fd_max_num];
};

// 为什么有的时候开辟的空间不需要清空，有的时候需要清空
// 首先区别在于，你开的空间可能不是全部都用，而是用多少覆盖多少空间，你读取的数据也都是覆盖过的，然后还有对应的结束策略（\n,0等）
// 所以不会读取到错乱的数据位置
// 但是例如fd_set rfds这种，开辟的是一个位图的空间，那么整个空间都是我们需要的
// 但是我们在写数据时，可能不会覆盖空间内所有的地方，所有有的地方没有被覆盖的化，就会有错乱的数据，所以要进行清空处理
